/*
 * Engine.java
 *
 * The main goal of this class is to provide tools
 * to analyse the position, and give replies as a
 * virtual opponent
 * w
 */
package strugglechess;

import java.util.ArrayList;
import java.util.Calendar;

/**
 *
 * @author Olivier Herau
 */
public class Engine {

    // TODO put those constant values in a separate class
    // Empty square
    final int EMPTY = 0;
    // White pieces
    final int WP = 1;
    final int WN = 2;
    final int WB = 3;
    final int WR = 4;
    final int WQ = 5;
    final int WK = 6;
    // Black pieces
    final int BP = 7;
    final int BN = 8;
    final int BB = 9;
    final int BR = 10;
    final int BQ = 11;
    final int BK = 12;
    final private int WHITE_TO_MOVE = 0;
    final private int BLACK_TO_MOVE = 1;
    private double[] coefficient = {1.0, 2.5, 2.5, 5.0, 9.0, 100000.0, -1.0, -2.5, -2.5, -5.0, -9.0, -100000.0};

    public Engine() {
    }

    public double evaluate(Position position) {
        double evaluation = 0.0;
        // TODO improve the evaluation method

        // Basic evaluation : just count the pieces for ech side with coefs

        int[] pieces = position.getPiecesLeft();
        for (int i = 0; i < 12; i++) {
            evaluation += pieces[i] * coefficient[i];
        }

        return evaluation;
    }

    /*
     * Give the list of possible legal moves
     */
    public ArrayList<Move> getPossibleMoves(Position position) {
        ArrayList<Move> moves = new ArrayList<Move>();

        int piece = EMPTY;
        for (int col = 1; col < 9; col++) {
            for (int row = 1; row < 9; row++) {
                ArrayList<Move> pieceMoves = new ArrayList<Move>();
                piece = position.getPiece(col, row);
                if (piece != EMPTY && position.isPiecePlayable(col, row)) {
                    pieceMoves = getPossibleMoves(position, col, row);
                    // add possible moves to the list
                    moves.addAll(pieceMoves);
                }

            }
        }
        return moves;
    }

    /*
     * Indicate if the position is check
     */
    public boolean isCheck(Position position) {

        // TODO implement this method
        int turn = position.getTurn();

        // BE CAREFUL: consider only the current player
        return false;
    }

    /*
     * Indicate if the position is checkmate
     */
    public boolean isCheckmate(Position position) {
        return (isCheck(position) && getPossibleMoves(position).isEmpty());
    }

    /*
     * Indicate if the position is stalemate
     */
    public boolean isStalemate(Position position) {
        return ((!isCheck(position)) && getPossibleMoves(position).isEmpty());

    }

    /*
     * Get the possible moves for one piece
     */
    private ArrayList<Move> getPossibleMoves(Position position, int col, int row) {
        ArrayList<Move> possibleMoves = new ArrayList<Move>();
        int pieceCode = position.getPiece(col, row);

        if ((position.isBlackToMove() && position.isPieceBlack(pieceCode))
                || (position.isWhiteToMove() && position.isPieceWhite(pieceCode))) {

            switch (pieceCode) {
                case WP:
                case BP:
                    possibleMoves.addAll(getPossiblePawnMoves(position, col, row));
                    break;
                case WB:
                case BB:
                    possibleMoves.addAll(getPossibleBishopMoves(position, col, row));
                    break;
                case WN:
                case BN:
                    possibleMoves.addAll(getPossibleKnightMoves(position, col, row));
                    break;
                case WR:
                case BR:
                    possibleMoves.addAll(getPossibleRookMoves(position, col, row));
                    break;
                case WQ:
                case BQ:
                    possibleMoves.addAll(getPossibleQueenMoves(position, col, row));
                    break;
                case WK:
                case BK:
                    possibleMoves.addAll(getPossibleKingMoves(position, col, row));
                    break;
                default:
                    break;

            }
        }
        return possibleMoves;
    }

    /*
     * Get possible moves for King
     */
    private ArrayList<Move> getPossibleKingMoves(Position position, int col, int row) {

        ArrayList<Move> possibleKingMoves = new ArrayList<Move>();
        //int piece = position.getPiece(col, row);

        if (col < 8 && position.isEmptyOrOpponent(col + 1, row)) {
            Move move = new Move(col, row, col + 1, row);
            possibleKingMoves.add(move);
        }
        if (col < 8 && row < 8 && position.isEmptyOrOpponent(col + 1, row + 1)) {
            Move move = new Move(col, row, col + 1, row + 1);
            possibleKingMoves.add(move);
        }
        if (row < 8 && position.isEmptyOrOpponent(col, row + 1)) {
            Move move = new Move(col, row, col, row + 1);
            possibleKingMoves.add(move);
        }
        if (col > 1 && row < 8 && position.isEmptyOrOpponent(col - 1, row + 1)) {
            Move move = new Move(col, row, col - 1, row + 1);
            possibleKingMoves.add(move);
        }
        if (col > 1 && position.isEmptyOrOpponent(col - 1, row)) {
            Move move = new Move(col, row, col - 1, row);
            possibleKingMoves.add(move);
        }
        if (col > 1 && row > 1 && position.isEmptyOrOpponent(col - 1, row - 1)) {
            Move move = new Move(col, row, col - 1, row - 1);
            possibleKingMoves.add(move);
        }
        if (row > 1 && position.isEmptyOrOpponent(col, row - 1)) {
            Move move = new Move(col, row, col, row - 1);
            possibleKingMoves.add(move);
        }
        if (col < 8 && row > 1 && position.isEmptyOrOpponent(col, row - 1)) {
            Move move = new Move(col, row, col + 1, row - 1);
            possibleKingMoves.add(move);
        }

        // TODO implement Casteling moves

        return possibleKingMoves;
    }

    /*
     * Get possible moves for Queen
     */
    private ArrayList<Move> getPossibleQueenMoves(Position position, int col, int row) {
        ArrayList<Move> possibleQueenMoves = new ArrayList<Move>();
        possibleQueenMoves.addAll(getPossibleRookMoves(position, col, row));
        possibleQueenMoves.addAll(getPossibleBishopMoves(position, col, row));
        return possibleQueenMoves;
    }

    /*
     * Get possible moves for Rook
     */
    private ArrayList<Move> getPossibleRookMoves(Position position, int col, int row) {
        ArrayList<Move> possibleRookMoves = new ArrayList<Move>();

        int i;

        // Horizontal moves right
        for (i = 1; i < 8; i++) {
            if ((col + i) < 9 && position.isEmptyOrOpponent(col + i, row)) {
                Move move = new Move(col, row, col + i, row);
                possibleRookMoves.add(move);
                if (!position.isSquareEmpty(col + i, row)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }

        // Horizontal moves left
        for (i = 1; i < 8; i++) {
            if ((col - i) > 0 && position.isEmptyOrOpponent(col - i, row)) {
                Move move = new Move(col, row, col - i, row);
                possibleRookMoves.add(move);
                if (!position.isSquareEmpty(col - i, row)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }

        // Vertical moves up
        for (i = 1; i < 8; i++) {
            if ((row + i) < 9 && position.isEmptyOrOpponent(col, row + i)) {
                Move move = new Move(col, row, col, row + i);
                possibleRookMoves.add(move);
                if (!position.isSquareEmpty(col, row + i)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }
        // Vertical moves down
        for (i = 1; i < 8; i++) {
            if ((row - i) > 0 && position.isEmptyOrOpponent(col, row - i)) {
                Move move = new Move(col, row, col, row - i);
                possibleRookMoves.add(move);
                if (!position.isSquareEmpty(col, row - i)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }

        return possibleRookMoves;
    }

    /*
     * Get possible moves for Knight
     */
    private ArrayList<Move> getPossibleKnightMoves(Position position, int col, int row) {
        ArrayList<Move> possibleKnightMoves = new ArrayList<Move>();
        //int piece = position.getPiece(col, row);

        if (col > 1 && row < 7 && position.isEmptyOrOpponent(col - 1, row + 2)) {
            Move move = new Move(col, row, col - 1, row + 2);
            possibleKnightMoves.add(move);
        }
        if (col < 8 && row < 7 && position.isEmptyOrOpponent(col + 1, row + 2)) {
            Move move = new Move(col, row, col + 1, row + 2);
            possibleKnightMoves.add(move);
        }
        if (col < 7 && row < 8 && position.isEmptyOrOpponent(col + 2, row + 1)) {
            Move move = new Move(col, row, col + 2, row + 1);
            possibleKnightMoves.add(move);
        }
        if (col < 7 && row > 1 && position.isEmptyOrOpponent(col + 2, row - 1)) {
            Move move = new Move(col, row, col + 2, row - 1);
            possibleKnightMoves.add(move);
        }
        if (col < 8 && row > 2 && position.isEmptyOrOpponent(col + 1, row - 2)) {
            Move move = new Move(col, row, col + 1, row - 2);
            possibleKnightMoves.add(move);
        }
        if (col > 1 && row > 2 && position.isEmptyOrOpponent(col - 1, row - 2)) {
            Move move = new Move(col, row, col - 1, row - 2);
            possibleKnightMoves.add(move);
        }
        if (col > 2 && row > 1 && position.isEmptyOrOpponent(col - 2, row - 1)) {
            Move move = new Move(col, row, col - 2, row - 1);
            possibleKnightMoves.add(move);
        }
        if (col > 2 && row < 8 && position.isEmptyOrOpponent(col - 2, row + 1)) {
            Move move = new Move(col, row, col - 2, row + 1);
            possibleKnightMoves.add(move);
        }

        return possibleKnightMoves;
    }

    /*
     * Get possible moves for Bishop TODO: debug index problem on this method
     */
    private ArrayList<Move> getPossibleBishopMoves(Position position, int col, int row) {
        ArrayList<Move> possibleBishopMoves = new ArrayList<Move>();

        int i;

        // Up-right moves
        for (i = 1; i < 8; i++) {
            if ((col + i) < 9 && (row + i) < 9 && position.isEmptyOrOpponent(col + i, row + i)) {
                Move move = new Move(col, row, col + i, row + i);
                possibleBishopMoves.add(move);
                if (!position.isSquareEmpty(col + i, row + i)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }

        // Down-right moves
        for (i = 1; i < 8; i++) {
            if ((col + i) < 9 && (row - i) > 0 && position.isEmptyOrOpponent(col + i, row - i)) {
                Move move = new Move(col, row, col + i, row - i);
                possibleBishopMoves.add(move);
                if (!position.isSquareEmpty(col + i, row - i)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }

        // Down-left moves
        for (i = 1; i < 8; i++) {
            if ((col - i) > 0 && (row - i) > 0 && position.isEmptyOrOpponent(col - i, row - i)) {
                Move move = new Move(col, row, col - i, row - i);
                possibleBishopMoves.add(move);
                if (!position.isSquareEmpty(col - i, row - i)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }
        // Up-left moves
        for (i = 1; i < 8; i++) {
            if ((col - i) > 0 && (row + i) < 9 && position.isEmptyOrOpponent(col - i, row + i)) {
                Move move = new Move(col, row, col - i, row + i);
                possibleBishopMoves.add(move);
                if (!position.isSquareEmpty(col - i, row + i)) {
                    i = 999;
                }
            } else {
                i = 999;
            }
        }

        return possibleBishopMoves;
    }

    /*
     * Get possible moves for Pawn
     */
    private ArrayList<Move> getPossiblePawnMoves(Position position, int col, int row) {
        ArrayList<Move> possiblePawnMoves = new ArrayList<Move>();
        int piece = position.getPiece(col, row);

        if (piece == WP && row < 8) {
            // Pawn's move
            if (position.getPiece(col, row + 1) == EMPTY) {
                Move move = new Move(col, row, col, row + 1);
                possiblePawnMoves.add(move);
            }
            if (row == 2
                    && (position.getPiece(col, row + 1) == EMPTY)
                    && (position.getPiece(col, row + 2) == EMPTY)) {
                Move move = new Move(col, row, col, row + 2);
                possiblePawnMoves.add(move);
            }
            // Pawn capture
            if (col < 8 && position.isPieceBlack(position.getPiece(col + 1, row + 1))) {
                Move move = new Move(col, row, col + 1, row + 1);
                possiblePawnMoves.add(move);
            }
            if (col > 1 && position.isPieceBlack(position.getPiece(col - 1, row + 1))) {
                Move move = new Move(col, row, col - 1, row + 1);
                possiblePawnMoves.add(move);
            }
            // TODO implement "en passant"

        }
        if (piece == BP && row > 1) {
            // Pawn's move
            if (position.getPiece(col, row - 1) == EMPTY) {
                Move move = new Move(col, row, col, row - 1);
                possiblePawnMoves.add(move);
            }
            if (row == 7
                    && (position.getPiece(col, row - 1) == EMPTY)
                    && (position.getPiece(col, row - 2) == EMPTY)) {
                Move move = new Move(col, row, col, row - 2);
                possiblePawnMoves.add(move);
            }
            // Pawn capture
            if (col < 8 && position.isPieceWhite(position.getPiece(col + 1, row - 1))) {
                Move move = new Move(col, row, col + 1, row - 1);
                possiblePawnMoves.add(move);
            }
            if (col > 1 && position.isPieceWhite(position.getPiece(col - 1, row - 1))) {
                Move move = new Move(col, row, col - 1, row - 1);
                possiblePawnMoves.add(move);
            }
            // TODO implement "en passant"
        }

        return possiblePawnMoves;
    }

    /*
     * Returns true is the move from c1,r1 to c2,r2 is possible
     */
    public boolean isPossibleMove(Position position, int c1, int r1, int c2, int r2) {
        ArrayList<Move> possibleMoves = getPossibleMoves(position, c1, r1);
        for (int i = 0; i < possibleMoves.size(); i++) {
            if (possibleMoves.get(i).col1 == c1
                    && possibleMoves.get(i).row1 == r1
                    && possibleMoves.get(i).col2 == c2
                    && possibleMoves.get(i).row2 == r2) {
                return true;
            }
        }
        return false;
    }

    /*
     * fonction ALPHABETA(P, A, B) // A < B si P est une feuille alors retourner
     * la valeur de P sinon Meilleur = –INFINI pour tout fils Pi de P faire Val
     * = -ALPHABETA(Pi,-B,-A) si Val > Meilleur alors Meilleur = Val si Meilleur
     * > A alors A = Meilleur si A ≥ B alors retourner Meilleur finsi finsi
     * finsi finpour retourner Meilleur finsi fin
     */
    /**
     *
     * @param P root position
     * @param alpha
     * @param beta
     * @param depth max depth to search (put MAX_INT if not needed)
     * @param timer time when process must stop
     * @return
     */
    private double alphaBeta(Position P, double alpha, double beta, int depth, int timer) {
        if (isLeave(P, depth, timer)) {
            return evaluate(P);
        } else {
            double best = Double.MIN_VALUE;
            for (Move move : P.getPossibleMoves()) {
                Position Pi = new Position(P);
                Pi.move(move);

                double Val = -alphaBeta(Pi, -beta, -alpha, depth - 1, timer);
                if (Val > best) {
                    best = Val;
                    if (best > alpha) {
                        alpha = best;
                        if (alpha >= beta) {
                            return best;
                        }
                    }
                }
            }
            return best;
        }
    }

    /*
     * Returns true if the position is a leave i.e. : - no possible position
     * after this one - depth value is zero - timer deadline value is reached
     */
    private boolean isLeave(Position P, int depth, int timer) {
        Calendar cal = Calendar.getInstance();
        return (P.getPossibleMoves().isEmpty()
                || depth <= 0
                || timer >= cal.getTimeInMillis());
    }

    /**
     *
     * @param P
     * @param depth
     * @param timer
     * @return
     */
    public Move getBestMove(Position P, int depth, int timer) {
        // TODO: incorrectet implementation for timer constraint usage - to be changed
        Move result;
        ArrayList<Move> possibleMoves = P.getPossibleMoves();
        ArrayList<Double> scores = new ArrayList<Double>(possibleMoves.size());
        int i_max = Integer.MIN_VALUE;
        Double score_max = Double.MIN_VALUE;
        Double score;
        for (int i = 0; i < possibleMoves.size(); i++) {
            Move m = possibleMoves.get(i);
            Position Pi = new Position(P);
            Pi.move(m);
            score = alphaBeta(Pi, Double.MIN_VALUE, Double.MAX_VALUE, depth, timer);
            scores.set(i, score);
            if (score > score_max) {
                score_max = score;
                i_max = i;
            }
        }

        result = possibleMoves.get(i_max);

        return result;
    }
}
